using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using FuncWorks.XNA.XTiled;
using System.IO;
using Ruminate.GUI.Content;
using Ruminate.GUI.Framework;
using fess.Screens;
using fess.Graphics;
using fess.Maps;
using fess.RPG;

namespace fess
{
  /// <summary>
  /// This is the main type for your game
  /// </summary>
  class FessGame : Microsoft.Xna.Framework.Game
  {
    public enum CemeteryQuestStatus { Unknown, Began, Undeads, Done, Reported };
    public enum CaveQuestStatus { Unknown, Began, Fighting, Done, Reported };

    public const double FireballCost = 10, FireArrowCost = 5, TurnUndeadCost = 7;

    GraphicsDeviceManager graphics;
    public List<Screens.IScreen> screens = new List<Screens.IScreen>();

    public Fess fess;
    public ActionCharacter daenur;
    public Home home;
    public Cemetery cemetery;
    public Tavern tavern;
    public CaveOut caveOut;

    public CemeteryQuestStatus CemeteryQuest = CemeteryQuestStatus.Unknown;
    public CaveQuestStatus CaveQuest = CaveQuestStatus.Unknown;
    public bool turnUndead = false, fireBonus = false, undeadBonus = false;
    public int rank = 0;

    public TimeSpan time = TimeSpan.Zero, elapsedTime = TimeSpan.Zero;

    List<IActive> activities = new List<IActive>();
    Screens.Adventure adventure;
    bool screenSwitch = false;

    public Gui gui;

    public FessGame()
    {
      graphics = new GraphicsDeviceManager(this);
      Content.RootDirectory = "Content";
      this.IsMouseVisible = true;
    }

    /// <summary>
    /// Allows the game to perform any initialization it needs to before starting to run.
    /// This is where it can query for any required services and load any non-graphic
    /// related content.  Calling base.Initialize will enumerate through any components
    /// and initialize them as well.
    /// </summary>
    protected override void Initialize()
    {
      base.Initialize();
    }

    /// <summary>
    /// LoadContent will be called once per game and is the place to load
    /// all of your content.
    /// </summary>
    protected override void LoadContent()
    {
      UIResources.RedLine = Content.Load<Texture2D>("UI/RedLine");
      UIResources.BlueLine = Content.Load<Texture2D>("UI/BlueLine");

      FXResources.smallHit1 = new FXRes(new SpriteSheet(Content.Load<Texture2D>("FX/smallHit1"), new Point(9, 14), new Vector2(9, 7)),
        TimeSpan.FromSeconds(0.2));
      FXResources.smallHit2 = new FXRes(new SpriteSheet(Content.Load<Texture2D>("FX/smallHit2"), new Point(17, 15), new Vector2(8, 7),
        (float)Math.PI),
        TimeSpan.FromSeconds(0.2));
      FXResources.blood = new FXRes(new SpriteSheet(Content.Load<Texture2D>("FX/blood"), new Point(32, 32), new Vector2(16, 16)),
        TimeSpan.FromSeconds(0.1));
      FXResources.fire1 = new FXRes(new SpriteSheet(Content.Load<Texture2D>("FX/explosion_4"), new Point(128, 128), new Vector2(64, 64)),
        TimeSpan.FromSeconds(0.5));
      FXResources.fire2 = new FXRes(new SpriteSheet(Content.Load<Texture2D>("FX/explosion1"), new Point(256, 256), new Vector2(128, 128)),
        TimeSpan.FromSeconds(0.2));

      RPGResources.SkeletonFace = Content.Load<Texture2D>("chars/skeleton-face");
      RPGResources.SkeletonWalk = new SpriteSheet(Content.Load<Texture2D>("chars/skel1"), new Point(32, 32), new Vector2(16, 22));
      RPGResources.BloodySkeletonWalk = new SpriteSheet(Content.Load<Texture2D>("chars/skel2"), new Point(32, 32), new Vector2(16, 22));

      var font = Content.Load<SpriteFont>("Basic");
      var UIImageMapDesc = File.ReadAllText("Content/UI/Map.txt");
      var UIImageMap = Content.Load<Texture2D>("UI/ImageMap");
      gui = new Gui(this, new Ruminate.GUI.Framework.Skin(UIImageMap, UIImageMapDesc),
        new Ruminate.GUI.Framework.Text(font, Color.White));
 

      screens.Add(new Screens.Splash(this));
    }

    public void NewGame()
    {
      time = TimeSpan.Zero;
      elapsedTime = TimeSpan.Zero;
      activities.Clear();


      fess = new Fess(this);
      daenur = new Daenur(this);

      home = new Home(this);
      cemetery = new Cemetery(this);
      tavern = new Tavern(this);
      caveOut = new CaveOut(this);     

      screens.Add(adventure = new Screens.Adventure(this));

      foreach (var map in new GenericMap[] { home, cemetery, tavern, caveOut })
      {
        GenericMap m = map;
        map.OnActivate += () => adventure.SwitchMap(m);
      }

      home.Activate();
    }

    /// <summary>
    /// UnloadContent will be called once per game and is the place to unload
    /// all content.
    /// </summary>
    protected override void UnloadContent()
    {
    }

    /// <summary>
    /// Allows the game to run logic such as updating the world,
    /// checking for collisions, gathering input, and playing audio.
    /// </summary>
    /// <param name="gameTime">Provides a snapshot of timing values.</param>
    protected override void Update(GameTime gameTime)
    {
      Misc.UpdateInput();

      for (int i = 0; i < activities.Count; ++i)
        if (!activities[i].Update(time))
        {
          activities.RemoveAt(i);
          --i;
        }

      if(IsInDialog && Misc.IsKeyJustPressed(Keys.Space))
        SkipMessage();

      if (screens.Count == 0)
        Exit();
      else if (!screens[0].Update(gameTime) || screenSwitch)
      {
        screenSwitch = false;
        screens.RemoveAt(0);
      }

      base.Update(gameTime);
    }

    public void SkipMessage()
    {
      foreach (var a in activities)
      {
        var dialog = a as Dialog;
        if (dialog != null)
        {
          dialog.Skip();
          break;
        }
      }
    }

    public void CancelDialog()
    {
      foreach (var a in activities)
      {
        var dialog = a as Dialog;
        if (dialog != null)
        {
          dialog.Cancel();
          break;
        }
      }
    }

    /// <summary>
    /// This is called when the game should draw itself.
    /// </summary>
    /// <param name="gameTime">Provides a snapshot of timing values.</param>
    protected override void Draw(GameTime gameTime)
    {
      if (screens.Count > 0)
      {
        GraphicsDevice.Clear(Color.CornflowerBlue);
        screens[0].Draw(gameTime);
      }

      base.Draw(gameTime);
    }

    public void AddActivity(IActive activity)
    {
      activities.Add(activity);
    }

    public bool IsInDialog { get { return activities.Any(a => a is Dialog); } }

    public void SwitchScreen(IScreen newScreen)
    {
      screens.Add(newScreen);
      screenSwitch = true;
    }
  }

  static class Misc
  {
    public static Random Rand = new Random();

    public static float RandAngle()
    {
      return (float)(Math.PI * 2 * Rand.NextDouble());
    }

    public static Vector2 ModArg(float module, float argument)
    {
      return new Vector2((float)Math.Cos(argument), (float)Math.Sin(argument)) * module;
    }

    public static Vector2 RandVector(float maxRadius, float minRadius = 0)
    {
      return ModArg((float)Rand.NextDouble() * (maxRadius - minRadius) + minRadius, RandAngle());
    }

    public static Vector2 LimitLength(this Vector2 vec, float maxLength)
    {
      float dist = vec.Length();
      if (dist == 0 || dist <= maxLength)
        return vec;
      else
        return vec * maxLength / dist;
    }

    public static int Dice(int number, int max)
    {
      if (max == 0)
        return 0;

      int result = 0;
      for (int i = 0; i < number; ++i)
        result += Rand.Next(max) + 1;

      return result;
    }

    public static Vector2 ToVector2(this Point pt)
    {
      return new Vector2(pt.X, pt.Y);
    }

    public static Point ToPoint(this Vector2 pt)
    {
      return new Point((int)pt.X, (int)pt.Y);
    }

    static KeyboardState previousKeyboardState, currentKeyboardState;
    static MouseState previousMouseState, currentMouseState;

    public static void UpdateInput()
    {
      previousKeyboardState = currentKeyboardState;
      previousMouseState = currentMouseState;

      currentKeyboardState = Keyboard.GetState();
      currentMouseState = Mouse.GetState();
    }

    public static bool IsKeyJustPressed(Keys key)
    {
      return currentKeyboardState.IsKeyDown(key) && previousKeyboardState.IsKeyUp(key);
    }

    public static bool IsMouseButtonJustClicked(bool leftButton)
    {
      return (leftButton ? currentMouseState.LeftButton : currentMouseState.RightButton) == ButtonState.Pressed &&
        (leftButton ? previousMouseState.LeftButton : previousMouseState.RightButton) == ButtonState.Released;
    }

    public static bool IsAnyKeyJustPressed()
    {
      return currentKeyboardState.GetPressedKeys().Except(previousKeyboardState.GetPressedKeys()).Count() > 0 ||
        IsMouseButtonJustClicked(true) || IsMouseButtonJustClicked(false);
    }

    public static bool RealIntersects(this Polygon p, Rectangle r)
    {
      return p.Intersects(r) || p.Contains(r);
    }
  }
}
